/*******************************************************************************
 * Copyright (c) 2011 Jovansonlee B. Cesar.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Jovansonlee B. Cesar - initial API and implementation
 ******************************************************************************/
package com.ivanceras.server.db;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.datastore.Transaction;
import com.ivanceras.server.util.CStringUtils;
import com.ivanceras.shared.db.DAO;
import com.ivanceras.shared.db.Filter;
import com.ivanceras.shared.db.IDatabase;
import com.ivanceras.shared.db.Join;
import com.ivanceras.shared.db.ModelDef;
import com.ivanceras.shared.db.Order;
import com.ivanceras.shared.db.SchemaTable;
import com.ivanceras.shared.exception.DatabaseException;
import com.ivanceras.shared.util.Configuration;

public class DB_BigTable implements IDatabase{

	DatastoreService datastore = null;
	Transaction txn = null;
	private boolean isTransacted;
	
	public DB_BigTable(String schema){
		System.out.println("Using "+DatabaseManager.BIGTABLE+" with schema: "+schema);
		datastore = DatastoreServiceFactory.getDatastoreService();
	}

	@Override
	public boolean createModel(ModelDef model) throws DatabaseException {
		return false;
	}


	@Override
	public DAO[] select(ModelDef model, Join join, Filter[] filters,
			Integer offset, Integer limit, Boolean doCount, Order[] orders)
	throws DatabaseException {
		if(orders != null){
			System.out.println(model.getModelName()+" Order[]: "+Arrays.asList(orders));
		}
		long[] ids = selectIds(model, join, filters, offset, limit, orders);
		DAO[] recordObjs = new DAO[ids.length];
		String[] attributes = model.getAttributes();
		String[] dataTypes = model.getDataTypes();
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			recordObjs[i] = new DAO(model.getModelName());
			Entity entity;
			try {
				entity = datastore.get(key);
				for(int j = 0; j < attributes.length; j++){
					Object value = entity.getProperty(attributes[j]);
					value = getFromBigTableValue(value, dataTypes[j]);
					//System.out.println(attributes[j]+": "+value);
					//recordObjs[i] = DataTypeUtils.set_DaoValue(recordObjs[i], attributes[j], value, dataTypes[CStringUtils.indexOf(attributes, attributes[j])]);
					recordObjs[i].set_Value(attributes[j], value);
				}
			} catch (EntityNotFoundException e) {
				e.printStackTrace();
				throw new DatabaseException("Entity not found!");
			} 
		}
		return recordObjs;
	}


	
	@Override
	public int delete(ModelDef model, Filter[] filters)
	throws DatabaseException {
		NamespaceManager.set(model.getNamespace());
		long[] ids = selectIds(model, null, filters, null, null, null);
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			datastore.delete(key);
		}
		return ids.length;
	}

	@Override
	public boolean drop(ModelDef model) throws DatabaseException {
		NamespaceManager.set(model.getNamespace());
		empty(model);
		return true;
	}

	@Override
	public int empty(ModelDef model) throws DatabaseException {
		//TODO:
		throw new DatabaseException("not yet implemented");
	}


	HashMap<String, Entity> entityList = new HashMap<String, Entity>();
	
	@Override
	public DAO insert(DAO dao, ModelDef model) throws DatabaseException {
		String namespace = model.getNamespace();
		if(namespace != null){
			NamespaceManager.set(namespace);
		}
		Entity entity = new Entity(model.getModelName());
		String[] attributes = model.getAttributes();
		for(int i = 0; i < attributes.length; i++){
			Object value = convertDaoValueToDB(dao, attributes[i]);
			entity.setProperty(attributes[i], value);
		}
		Key key = datastore.put(entity);
		dao.set_Value(model.getPrimaryColumn(),key);
		return dao;
	}

	@Override
	public DAO update(DAO dao, ModelDef model, Filter[] filters)
	throws DatabaseException {
		String namespace = model.getNamespace();
		if(namespace != null){
			NamespaceManager.set(namespace);
		}
		long[] ids = selectIds(model, null, filters, null, null, null);
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			Entity entity;
			try {
				entity = datastore.get(key);
				String[] attributes = model.getAttributes();
				for(int j = 0; j < attributes.length; j++){
					Object value = convertDaoValueToDB(dao, attributes[j]);
					entity.setProperty(attributes[j], value);
				}
				datastore.put(entity);
			} catch (EntityNotFoundException e) {
				e.printStackTrace();
				throw new DatabaseException("Entity not found!");
			}

		}
		return dao;
	}

	public long[] selectIds(ModelDef model, Join join, Filter[] filters, Integer offset, Integer limit, Order[] orders) throws DatabaseException {
		String namespace = Configuration.dbSchema;
		if(namespace != null){
			NamespaceManager.set(namespace);
		}
		if(join != null){
			System.err.println("model 1: "+join.getFirstModel().getModelName());
			System.err.println("model 2: "+join.getForeignModel().getModelName());
		}
		Query q = new Query(model.getModelName());
		String[] attributes = model.getAttributes();
		String[] dataTypes = model.getDataTypes();
		if(filters != null){
			for(Filter f : filters){
				String[] filterColumn = f.attribute.split("\\.");
				String column = filterColumn[filterColumn.length-1];
//				if(join != null){
//					System.err.println(" "+filterColumn+" "+f.operator+" "+f.value);
//				}
				//System.err.println("filter: "+f.toString());
				String dataType = dataTypes[CStringUtils.indexOf(attributes, column)];
				q.addFilter(f.attribute, getEquivOp(f.operator), getFilterValue(f.value, dataType));
				if(orders != null && orders.length > 0){
					for(Order order : orders){
						String orderColumn = order.getColumn();
						SortDirection sortDirection = order.isAscending() ? SortDirection.ASCENDING : SortDirection.DESCENDING;
						System.out.println("order by "+orderColumn+" "+sortDirection.name());
						q.addSort(orderColumn, sortDirection);
					}
				}
			}
		}
		System.out.println("Query: "+q.toString());
		PreparedQuery pq = datastore.prepare(q);
		Iterator<Entity> results = null;
		if(limit != null && offset != null){
			results = pq.asIterator(FetchOptions.Builder.withLimit(limit).offset(offset));
		}else{
			results = pq.asIterator();
		}
		List<Long> ids = new ArrayList<Long>();
		while(results.hasNext()){
			Entity ent = results.next();
			long id = ent.getKey().getId();
			//System.out.println("id: "+id);
			ids.add(id);
		}
		long ids_l[] = new long[ids.size()];
		for(int i = 0; i < ids.size(); i++){
			ids_l[i] = ids.get(i);
		}
		return ids_l;
	}

	private Query.FilterOperator getEquivOp(String operator) throws DatabaseException{
		if(operator.equals(Filter.EQUAL)) return Query.FilterOperator.EQUAL;
		if(operator.equals(Filter.GREATER_THAN)) return Query.FilterOperator.GREATER_THAN;
		if(operator.equals(Filter.GREATER_THAN_OR_EQUAL)) return Query.FilterOperator.GREATER_THAN_OR_EQUAL;
		if(operator.equals(Filter.IN)) return Query.FilterOperator.IN;
		if(operator.equals(Filter.LESS_THAN)) return Query.FilterOperator.LESS_THAN;
		if(operator.equals(Filter.LESS_THAN_OR_EQUAL)) return Query.FilterOperator.LESS_THAN_OR_EQUAL;
		if(operator.equals(Filter.NOT_EQUAL)) return Query.FilterOperator.NOT_EQUAL;
		if(operator.equals(Filter.LIKE)) throw new DatabaseException("Can not use Like in DB_BigTable");
		return Query.FilterOperator.EQUAL;
	}


	@Override
	public void beginTransaction() throws DatabaseException {
		txn = datastore.beginTransaction();
		isTransacted = false;
	}

	@Override
	public void commitTransaction() throws DatabaseException {
		txn.commit();
		isTransacted = true;
	}

	@Override
	public void rollbackTransaction() throws DatabaseException {
		txn.rollback();
		isTransacted = true;
	}

	@Override
	public boolean isTransacted() throws DatabaseException {
		return isTransacted;
	}

	@Override
	public boolean isClosed() {
		return datastore == null;
	}

	@Override
	public boolean reset() {
		return true;
	}


	@Override
	public Integer getTotalRecords(String schema, String modelName)
			throws DatabaseException {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public ModelDef getModelMetaData(String schema, String tableName) throws DatabaseException {
		// TODO Auto-generated method stub
		return null;
	}


//	@Override
//	public ResultSet executeSelectSQL(String sql, Object[] parameters)
//			throws DatabaseException {
//		// TODO Auto-generated method stub
//		return null;
//	}

	@Override
	public SchemaTable[] getTableNames(String schema, String tablePattern)
			throws DatabaseException {
		throw new DatabaseException("This is not implemented in BigTable");
	}

	protected Object convertDaoValueToDB(DAO dao, String column){
		if(dao == null) return null;
		Object value = dao.get_Value(column);
		if(value == null){
			return null;
		}
		if(value.getClass().equals(java.math.BigDecimal.class)){
			return ((BigDecimal)value).doubleValue();
		}
		else if(value.getClass().equals(java.math.BigInteger.class)){
			return ((BigInteger)value).longValue();
		}
		else if(value.getClass().equals(java.lang.String.class) && value != null && ((String)value).length() > 500){
			Text text = new Text((String)value);
			return text;
		}
		else{
			return value;
		}
	}
	public static Object getFilterValue(Object value, String dataType){
		if(value == null){
			return null;
		}
		if(dataType.equals("java.math.BigDecimal")){
			return ((BigDecimal)value).doubleValue();
		}
		else if(dataType.equals("java.math.BigInteger")){
			return ((BigDecimal)value).longValue();
		}
		else if(value.getClass().equals(java.lang.String.class) && value != null && ((String)value).length() > 500){
			Text text = new Text((String)value);
			return text;
		}
		else{
			return value;
		}
	}
	
	/**
	 * From the rdbms database to serializable DAO
	 * @param dao
	 * @param column
	 * @param value
	 * @return
	 */
	private static Object getFromBigTableValue(Object value, String dataType){
		if(value == null){
			return null;
		}
		if(dataType.equals("java.lang.String") && value != null && value.getClass().equals(Text.class)){
			String strvalue = ((Text)value).getValue();
			return strvalue;
		}
		else if(dataType.equals("java.lang.Integer")){
			return (Integer) value;
		}
		else if(dataType.equals("java.lang.Boolean")){
			return (Boolean) value;
		}
		else if(dataType.equals("java.lang.Double")){
			return (Double) value;
		}
		else if(dataType.equals("java.lang.Float")){
			return (Float) value;
		}
		else if(dataType.equals("java.sql.Timestamp")){
			Date date = new Date();
			date.setTime(((Timestamp)value).getTime());
			return date;
		}
		else if(dataType.equals("java.math.BigDecimal")){
			BigDecimal bdvalue = new BigDecimal((Double)value);
			return bdvalue;
		}
		else if(dataType.equals("java.math.BigInteger")){
			return (BigInteger) value;
		}
		else{
			return value;
		}
	}

}
